
import pygst
pygst.require("0.10")
import gst
#import pygtk
#pygtk.require("2.0")
#import gtk
import gobject
import constants
import themixerexceptions
import random
import struct

#gtk.gdk.threads_init() #TODO - should it be here? shouldn't be gobject threads?

class UnexpectedInput(themixerexceptions.TheMixerException): pass
class IllegalArgument(themixerexceptions.TheMixerException): pass

def getPadNameRtpInfo(pad):
    name = pad.get_property("name")
    #recv_rtp_src_%d_%d_%d (session, ssrc, pt)
    tokens = name.split("_")
    session = int(tokens[3])
    ssrc = int(tokens[4])
    pt = int(tokens[5])
        
    return session, ssrc, pt

class FileInputSender(gobject.GObject):
    """
        Class for playing back file (archived audio).
        It sends the file via RTP to the localhost.
    """
    
    __gsignals__ = {
            'eos' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                    (gobject.TYPE_INT,)),
            
            'error' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                    (gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)),
            
    }
    
    def __init__(self, port):
        gobject.GObject.__init__( self )
        
        self._senders = {}
        self._idCount = 0
        self._port = port
        
    def _getAvailableID(self):
        """
            Called from L{addFileInput}
            @return id for identifying new audio that's played back
        """
        id = self._idCount
        self._idCount += 1
        return id
    
    def _messageHandler(self, bus, message, id):
        t = message.type
        
        if t == gst.MESSAGE_EOS:
            #bus.remove_signal_watch()
            self._senders[id].stop()
            self.emit('eos', id)
            
        elif t == gst.MESSAGE_ERROR:
            #bus.remove_signal_watch()
            print message
            self._senders[id].stop()
            err, debug = message.parse_error()
            self.emit('error', id, err, debug)
      
    def _decodebinPadAdded(self, dec, pad, last_pad, next_pad):
        pad.link(next_pad)
    
    def addFileInput(self, path):
        """
            Adds a new file input to the sender, it is automatically set to playing.
        
            @param path: path to the file to be played
            @return: a file input control
        """
        id = self._getAvailableID()
        pipe = gst.Pipeline("pipeline" + str(id))
        
        src = gst.element_factory_make("filesrc", "src")
        src.set_property("location", path)
        dec = gst.element_factory_make("decodebin", "dec")
        aconv = gst.element_factory_make("audioconvert", "aconv")
        ares = gst.element_factory_make("audioresample", "ares")
        enc = gst.element_factory_make("mulawenc", "enc")
        rtppay = gst.element_factory_make("rtppcmupay", "rtppay")
        sink = gst.element_factory_make("udpsink", "sink")
        sink.set_property("clients", "localhost:"+str(self._port))
        
        pipe.add(src)
        pipe.add(dec)
        pipe.add(aconv)
        pipe.add(ares)
        pipe.add(enc)
        pipe.add(rtppay)
        pipe.add(sink)
        
        src.link(dec)
        dec.connect('new-decoded-pad', self._decodebinPadAdded, aconv.get_pad("sink"))
        aconv.link(ares)
        ares.link(enc)
        enc.link(rtppay)
        rtppay.link(sink)
        # sets a new random ssrc to identify from the mixer
        ssrc = random.randint(1000000,9999999)
        rtppay.set_property('ssrc', ssrc)
        bus = pipe.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self._messageHandler, id)
        
        pipe.set_state(gst.STATE_PLAYING)

        self._senders[id] = FileInputControl(self, pipe, id, ssrc)
        
        return self._senders[id]
        
    def getFileInputControl(self, id):
        """
            @param id: id of the file player
            @return control: FileInputControl of the stream with id id
        """
        return self._senders[id]
    
    def hasStream(self, id):
        """
            Checks if a stream with an id exists
            @return: True if it exists
        """
        return id in self._pipes.keys()

    def remove(self, id):
        """
            Removes stream of an ID. If no stream has that id, nothing is done.
            @param id: the id of the stream to be removed 
        """
        if (id not in self._senders.keys()):
            return
        
        self._senders[id].stop()
        self._senders[id].getPipeline().get_bus().remove_signal_watch()
        del self._senders[id]
    
    def enqueueRemoval(self, id):
        """
            Enqueue a removal action later.
        
            Enqueue a removal of a stream, useful for calling inside a signal
            handler call (some deadlock issues might prevent from calling remove 
            directly).
            @param id: id of the stream to be removed. 
        """
        gobject.idle_add(self.remove, id)
            

class FileInputControl:
    """
        Class for controlling the file (archived audio) playback
        Returned by L{FileInputSender.getFileInputControl}
    """
    
    def __init__(self, parentSender, pipeline, id, ssrc):
        self.fileInputSender = parentSender
        self.pipe = pipeline
        self.id = id
        self.ssrc = ssrc
        
    def getSsrc(self):
        return self.ssrc
    
    def getID(self):
        return self.id
    
    def getPipeline(self):
        return self.pipe
    
    def play(self):
        self.pipe.set_state(gst.STATE_PLAYING)
    
    def stop(self):
        self.pipe.set_state(gst.STATE_NULL)
    
    def remove(self):
        self.fileInputSender.enqueueRemoval(self.id)


class RtpStreamsReceiver(gobject.GObject):
    """
        Responsible for receiving rtp input streams and playing them.
        It receives both live network streams and archived streams
    """
    
    __gsignals__ = {
        #notifies about eos (dummy int)
        'eos' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_INT,)),
            
        #notifies about ocurred errors (error, message)
        'error' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_STRING, gobject.TYPE_STRING)),
                    
        #new-rtp-stream(session, ssrc, pt, id) 
        'new-rtp-stream' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_UINT , gobject.TYPE_UINT , gobject.TYPE_UINT, gobject.TYPE_UINT )),
                    
        #rtp-stream-timeout(session, ssrc)
        'rtp-stream-timeout' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_UINT , gobject.TYPE_UINT , gobject.TYPE_UINT, gobject.TYPE_UINT ))
            
    }
    
    def __init__(self, port, enableLevelMeasurement=True, enableTimeShifting=True,
                 bufferStatsFilePrefix=None, play=True):
        """Creates a RtpStreamsReceiver. Responsible for listening on a UDP port for incoming
        RTP streams.
        
        @param port: port to listen to
        @param enableLevelMeasurement: If streams audio level should be inspected periodically
        @param enableTimeShifting: If the operations to do "timeshifting" should be enabled
        @param bufferStatsFilePrefix: default file path prefix to save rtp buffer statistics.
            Use None for disabling. (i.e. if it is 'stats/data' is provided, files for each stream
            are going to be saved inside stats directory (it should exist already), using file names that start
            with 'data' followed by specific stream parameters, like ssrc and session.
        @param play: If the pipeline should be set to playing at startup
        """
        gobject.GObject.__init__(self)
        
        self.pipe = None
        self.rtpbin = None
        self.inputIDCounter = 0
        self.port = port
        
        self._enableLevelMeasurement = enableLevelMeasurement
        self._enableTimeShift = enableTimeShifting
        self._bufferStatsFilePrefix = bufferStatsFilePrefix
        #        if (self._bufferStatsFilePrefix):
        #            #contains a map of id to tuples(file, filename)
        self._statsFileMap = {}
        
        self.inputs = {}
        self._initRtpPipeline()
        self._tivo = TivoManager(self.pipe)
        if(play):
            self.play()
    
    def _initRtpPipeline(self):
        """Initiates the basic elements of the pipeline"""
        
        self.pipe = gst.Pipeline("rtp-receiver")
        
        self.src = gst.element_factory_make("udpsrc", "src")
        self.src.set_property("port", self.port)
        self.src.set_property("caps", gst.Caps("application/x-rtp, clock-rate = (int) 8000"))
        self.rtpbin = gst.element_factory_make("gstrtpbin", "rtpbin")
        self.rtpbin.set_property("latency", 50)
        self.adder = gst.element_factory_make("liveadder", "adder")
        self.sink = gst.element_factory_make(constants.AUDIO_SINK, "audiosink")
        
        self.pipe.add(self.src)
        self.pipe.add(self.rtpbin)
        self.pipe.add(self.adder)
        self.pipe.add(self.sink)        
        
        self.src.link(self.rtpbin)
        self.adder.link(self.sink)    
        
        bus = self.pipe.get_bus()
        bus.add_signal_watch() #remove signal watch
        bus.connect('message', self._messageHandler)
        
        self.rtpbin.connect("pad-added", self._rtpbinNewPad)
        self.rtpbin.connect("on-timeout", self._rtpbinStreamTimeout)
    
    def _messageHandler(self, bus, message):
        t = message.type
        
        if t == gst.MESSAGE_ELEMENT:
            struct = message.structure
            name = struct.get_name()
            if ("level" in name):
                name = message.src.get_name()
                index = name.find('-')
                value = name[index+1:]
                self._measureLevel(value, struct)
        
        elif t == gst.MESSAGE_EOS:
            self.null()
            self.emit('eos', 0)
        
        elif t == gst.MESSAGE_ERROR:
            self.null()
            err, debug = message.parse_error()
            self.emit('error', err, debug)
    
    def _measureLevel(self, value, struct):
        """
            @param value: audio level 
        """
        pass
        #print "got level:", struct.to_string(), "from id:", str(id)
    
    def _getInput(self, session, ssrc):
        for x in self.inputs.values():
            if x.getSsrc() == ssrc and x.getSession() == session:
                return x
            
        return None
    
    def _rtpbinStreamTimeout(self, rtpbin, session, ssrc):
        input = self._getInput(session, ssrc)
        if (input == None):
            return
        
        self.emit('rtp-stream-timeout', input.getSession(), input.getSsrc(),
                  input.getPT(), input.getID())
        
    def _newDecodedPad(self, dec, pad, last, connectpad):
        #TODO protection against non-audio pads
        pad.link(connectpad)
    
    def _rtpbinNewPad(self, rtpbin, pad):
        session, ssrc, pt = getPadNameRtpInfo(pad)
        id = self._getNewID()
        if (self._enableTimeShift):
            tee = gst.element_factory_make("tee", "tee-" + str(id))
        else:
            tee = None
        queue = gst.element_factory_make("queue", "queue-" + str(id))
        rtpdepay = gst.element_factory_make(constants.depays[pt], "rtpdepay-" + str(id))
        
        #check if we should save buffer statistics
        if (self._bufferStatsFilePrefix != None):
            probed_pad = rtpdepay.get_pad("sink")
            self._initBufferStatsFile(id, str(id))
            probed_pad.add_buffer_probe(self._bufferStatsProbe, id)
            
                    
        dec = gst.element_factory_make("decodebin", "dec-"+ str(id))
        
        mix = MixerBin("mixer-" + str(id), id, self._enableLevelMeasurement)
        
        if (self._enableTimeShift):
            self.pipe.add(tee)
            self._tivo.addTee(id, tee)
        self.pipe.add(queue)
        self.pipe.add(rtpdepay)
        self.pipe.add(dec)
        self.pipe.add(mix)
        
        pad.link(rtpdepay.get_pad("sink"))
        rtpdepay.link(queue)
        queue.link(dec)
        if (self._enableTimeShift):
            tee.link(mix)
            connectpad = tee.get_pad("sink")
        else:
            connectpad = mix.get_pad("sink")
        
        pad = self.adder.get_request_pad("sink%d")
        mix.get_pad("src").link(pad)
        
        dec.connect("new-decoded-pad", self._newDecodedPad, connectpad)
        
        mix.sync_state_with_parent()
        if (self._enableTimeShift):
            tee.sync_state_with_parent()
        dec.sync_state_with_parent()
        queue.sync_state_with_parent()
        rtpdepay.sync_state_with_parent()
        
        netstream = RtpInput(session, ssrc, pt, id, Mixer(mix, None, id), tee)
        self.inputs[id] = netstream
        self.emit('new-rtp-stream', session, ssrc, pt, id)        
    
    def _getNewID(self):
        id = self.inputIDCounter
        self.inputIDCounter += 1
        return id
    
    def play(self):
        self.pipe.set_state(gst.STATE_PLAYING)
        
    def null(self):
        self.pipe.set_state(gst.STATE_NULL)
        self._closeStatsFile()
        
    def getMixer(self, id):
        if (id in self.inputs):
            return self.inputs[id].getMixer()
        else:
            raise IllegalArgument, "Invalid ID: " + str(id)
               
    def createTivo(self, idList, destFile):
        if (not self._enableTimeShift):
            raise FeatureDisabled, "Timemachine feature is disabled"
        tmu = self._tivo.startTivo(idList, destFile)
        return tmu   
    
    def _bufferStatsProbe(self, pad, buffer, id):
        file = self._statsFileMap[id][0]
        self._writeBufferStats(file, buffer)
        return True
    
    def _getBufferUsefulInfo(self, buffer):
        seqn = struct.unpack('H', buffer.data[2:4][::-1])[0]
        timestamp = struct.unpack('I', buffer.data[4:8][::-1])[0]
        return seqn, timestamp
        
    
    def _writeBufferStats(self, file, buffer):
        #write useful info
        seqn, timestamp = self._getBufferUsefulInfo(buffer)
        
        msg = [str(seqn), str(timestamp), "\n"]
        
        file.write(' '.join(msg))
    
    def _initBufferStatsFile(self, id, suffix):
        filename = self._bufferStatsFilePrefix + suffix
        file = open(filename, "w")
        self._statsFileMap[id] =  (file, filename)
        
        #no header :)
        #file.write("BUFFER STATISTICS FILE - " + str(id) + "\n")
        #file.write(20 * "#" + "\n")
        
    def _closeStatsFile(self):
        for x in self._statsFileMap.values():
            x[0].close()
        
        self._statsFileMap = {}


class RtpInput:
    
    def __init__(self, session, ssrc, pt, id, mixer, elementlist=None):
        self.session = session
        self.ssrc = ssrc
        self.pt = pt
        self.id = id
        self.mixer = mixer
        #elements involved in this part of the stream
        #should be in the order from upstream to dowstream 
        self.elementList = elementlist
        
    def getSession(self):
        return self.session
    
    def getSsrc(self):
        return self.ssrc
    
    def getPT(self):
        return self.pt
    
    def getID(self):
        return self.id
    
    def getMixer(self):
        return self.mixer
           

class TheMixerPipelineController(gobject.GObject):
    """
        Responsible for holding the mixer pipeline, and provides a methods to control it.
    
        Holds the mixer pipeline and provide methods to manage its state, flow and parameters. 
        Also provides ways to get information about it.
    
        TODO - put a graph representing the pipeline here
    """
    
    __gsignals__ = {
            'eos' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                    (gobject.TYPE_INT,)),
            
            'error' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                    (gobject.TYPE_STRING, gobject.TYPE_STRING)),
            
            'file-stream-ended' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                    (gobject.TYPE_INT,)),
                    
            #new-rtp-stream(session, ssrc, pt, id) 
            'new-rtp-stream' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                    (gobject.TYPE_UINT , gobject.TYPE_UINT , gobject.TYPE_UINT, gobject.TYPE_UINT )),

            #rtp-stream-timeout(session, ssrc)
            'rtp-stream-timeout' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                    (gobject.TYPE_UINT , gobject.TYPE_UINT , gobject.TYPE_UINT, gobject.TYPE_UINT ))
    }
    
    def __init__(self, name, port=0):
        gobject.GObject.__init__( self ) 
        #store all elements in a tuple (from downstream to upstream)
        #related to some input stream
        self.fileInputStreams = {}
        self.networkInputStreams = {}
        self.mixerIDCounter = 0
        self.messageListener = None
        self.gtkloop = False
        self.port = port
        self.udpsrc = None
        self.rtpbin = None
        self._initPipeline(name)
        self.toRemove = 0

    
    def _initRtpSrc(self):
        self.rtpbin = gst.element_factory_make("gstrtpbin", "rtpbin")
            
        self.udpsrc = gst.element_factory_make("udpsrc", "udpsrc")
        self.udpsrc.set_property("port", self.port)
        self.udpsrc.set_property("caps", gst.Caps("application/x-rtp, clock-rate = (int) 8000"))
        self.pipeline.add(self.rtpbin)
        self.pipeline.add(self.udpsrc)
        
        pad = self.rtpbin.get_request_pad("recv_rtp_sink_%d")
        udppad = self.udpsrc.get_pad("src")
        udppad.link(pad)

        self.rtcpsrc = gst.element_factory_make("udpsrc", "rtcpsrc")
        self.rtcpsrc.set_property("port", self.port+1)
        self.pipeline.add(self.rtcpsrc)
        pad = self.rtpbin.get_request_pad( "recv_rtcp_sink_%d" )
        rtcppad = self.rtcpsrc.get_pad("src")
        rtcppad.link( pad )
                
        self.rtpbin.connect("pad-added", self._newPad)
        self.rtpbin.connect("on-ssrc-sdes", self._handleSdes)
        self.rtpbin.connect("on-new-ssrc", self._newStream)
        self.rtpbin.connect("on-timeout", self._streamTimeout)
        
    
    def _initPipeline(self, name):
        """
            Inits the controller pipeline.
        
            Creates a pipeline, then instantiates an output sink and two adders,
            one for each channel, adding them to the pipeline and linking them.
        """
        self.pipeline = gst.Pipeline(name)
        self.sink = gst.element_factory_make(constants.AUDIO_SINK, "audiosink")
        self.pipeline.add(self.sink)
        
        self.adder = gst.element_factory_make("adder", "adder")
        
        self.pipeline.add(self.adder)
        
        if (self.port > 0):
            self._initRtpSrc()
            
        self.adder.link(self.sink)
        
        bus = self.pipeline.get_bus()
        bus.add_signal_watch() #remove signal watch
        bus.connect('message', self._messageHandler)        
        
    def _messageHandler(self, bus, message):
        #TODO - add some kind of message listeners
        t = message.type
        
        if t == gst.MESSAGE_EOS:
            if (self.gtkloop):
                pass
                #gtk.main_quit()
            self.pipeline.set_state(gst.STATE_NULL)
            self.emit('eos', 0)
            
        elif t == gst.MESSAGE_ERROR:
            if (self.gtkloop):
                pass
                #gtk.main_quit()
            self.pipeline.set_state(gst.STATE_NULL)
            err, debug = message.parse_error()
            self.emit('error', err, debug)
        if (self.messageListener != None):
            self.messageListener(message)
    
    def _getPadNameInfo(self, pad):
        name = pad.get_property("name")
        #recv_rtp_src_%d_%d_%d (session, ssrc, pt)
        tokens = name.split("_")
        session = int(tokens[3])
        ssrc = int(tokens[4])
        pt = int(tokens[5])
        
        return session, ssrc, pt
    
    def _removedPad(self, rtpbin, pad):
        print pad
    
    def _newPad(self, element, pad):
        
        session, ssrc, pt = self._getPadNameInfo(pad)
        print "New PAD being created", session, pt, ssrc

        id = self.mixerIDCounter
        self.mixerIDCounter += 1
        unique = str(session) + "-" + str(ssrc) + "-" + str(pt) + "-" + str(id)
        rtpdepay = gst.element_factory_make(constants.depays[pt], "rtp-" + unique)
        self.pipeline.add(rtpdepay)
        pad.link(rtpdepay.get_pad("sink"))
        mixer = MixerBin("rtp-mixer-" + unique)
        self.pipeline.add(mixer)
        rtpdepay.link(mixer)
        adder_pad = self.adder.get_request_pad("sink%d")
        mixer.get_pad("src").link(adder_pad)
        

        mixercontrol = Mixer(mixer, self, id)
        netManager = NetworkInputManager([rtpdepay], mixer, mixercontrol, session, ssrc, pt)
        self.networkInputStreams[id] = netManager
        
        mixer.sync_state_with_parent()
        rtpdepay.sync_state_with_parent()
      
        self.emit('new-rtp-stream', session, ssrc, pt, id)
    
    def _newStream(self, a1, a2, ud ):
        print "New Stream appeared:", a1, a2, ud
        
    def _handleSdes(self, a1, a2, ud ):
        print "New SDES:", a1.get_property('sdes-cname'), a2, ud
        
    def _streamTimeout(self, rtpbin, session, ssrc):
        manager = self._getNetworkManagerByRTPInfo(session, ssrc)
        if (manager != None):
            manager.setInactive()
            self.emit('rtp-stream-timeout', session, ssrc, manager.getPt(), manager.getID())
        
#    DEPRECATED
#    def addMessageListener(self, messageListener):
#        self.messageListener = messageListener
#    
    def _getNetworkManagerByRTPInfo(self, session, ssrc):
        for x in self.networkInputStreams.values():
            if x.getSession() == session and x.getSsrc() == ssrc:
                return x
            
        return None

    def getInputCount(self):
        count = 0
        for x in self.fileInputStreams.values():
            if (x.isActive()):
                count += 1
        for x in self.networkInputStreams.values():
            if (x.isActive()):
                count += 1
        return count - self.toRemove
    
    def _padEventProbeCB(self, pad, event, id):
        if event.type == gst.EVENT_EOS:
            self.getMixer(id).setInactive()
            self.emit('file-stream-ended', id)

        return True
    
    def addInput(self, inputElement, inputPad=None):
        if (inputPad == None):
            inputPad = inputElement.get_pad("src")
        
        id = self.mixerIDCounter
        
        inputElement.set_property("name", "input-" + str(id))
        inputBin = MixerBin("input-bin-" + str(self.mixerIDCounter))

        self.pipeline.add(inputElement)
        self.pipeline.add(inputBin)
        
        
        inputElement.link(inputBin)
        inputBin.link(self.adder)
        pad = inputBin.get_pad("src")
        probeId = pad.add_event_probe(self._padEventProbeCB, id)
        
        mixer = Mixer(inputBin, self, id)
        fileInputManager = FileInputManager([inputElement], inputBin, mixer)
        if (self.fileInputStreams.has_key(id)):
            print "Unallowed input" #TODO - raise exception
            pass
        self.fileInputStreams[id] = fileInputManager
        self.mixerIDCounter += 1
        
        inputBin.sync_state_with_parent()
        inputElement.sync_state_with_parent()
        
        return mixer
    
    def addFileInput(self, filepath):
        """
            Adds a new file input to the pipeline.
        
            @param filepath:path to the file to be read
            @return: True if succeeded, False otherwise
        """
        
        input = gst.element_factory_make("filesrc", "filesrc")
        input.set_property("location", filepath)
        return self.addInput(input, None)
 
    def play(self):
        if (self.getInputCount() == 0):
            pass #TODO
        self.pipeline.set_state(gst.STATE_PLAYING)
    
    def getPipelineStatus(self):
        return self.pipeline.get_state()
    
    def stop(self):
        self.pipeline.set_state(gst.STATE_READY)
    
    def null(self):
        self.pipeline.set_state(gst.STATE_NULL)
    
    def pause(self):
        self.pipeline.set_state(gst.STATE_PAUSED) # TODO - check if it is blocked what should be done
    
    def playAndBlock(self):
        self.play()
        self.gtkloop = True
        #gtk.main()
        
    def remove(self, index):
        self.toRemove -= 1
        inputManager = None
        error = False
        try:
            inputManager = self.fileInputStreams[index]
        except:
            try:
                inputManager = self.networkInputStreams[index]
            except:
                error = True
        
        if (error):
            raise IllegalArgument, "input with ID " + str(index) + " not found"
        
        if (self.getInputCount() == 1):
            #this is the last guy, so we set everything to null
            self.null()
            self.emit('eos', 0)
            
        inputManager.setInactive()
        inputbin = inputManager.getMixerBin()
        pad = (inputbin.get_pad("src")).get_peer()
        if (pad == None):
            return

        self.adder.release_request_pad(pad)
        
        inputbin.set_state(gst.STATE_NULL)
        for x in inputManager.getInputElements():
            x.set_state(gst.STATE_NULL)
        
        self.pipeline.remove(inputbin)
            
        for x in inputManager.getInputElements():
            self.pipeline.remove(x)
            
        del self.fileInputStreams[index]
        
    def getMixer(self, id):
        if (id in self.fileInputStreams):
            return self.fileInputStreams[id].getMixer()
        if (id in self.networkInputStreams):
            return self.networkInputStreams[id].getMixer()
        
        return None
    
    def enqueueRemoval(self, id):
        self.toRemove += 1
        gobject.idle_add(self.remove, id)
    
gobject.type_register( TheMixerPipelineController )
    
class MixerBin(gst.Bin):
    
    def __init__(self, name, id, enableLevel=True):
        """
            Creates a new input bin.
        
            @param input:gstreamer element that must have one source pad 
        """
        super(MixerBin,self).__init__(name)   
       
        self.id = id
        self._enableLevel = enableLevel
        self._initBaseElements()
        self._initGhostPads()
        
    
    def _initBaseElements(self):
        self.volume = gst.element_factory_make("volume", "volume-" + str(self.id))
        self.panorama = gst.element_factory_make("audiopanorama", "panorama-"+ str(self.id))
        self.add(self.volume)
        self.add(self.panorama)
        
        self.panorama.link(self.volume)
        
        if (self._enableLevel):
            self.level = gst.element_factory_make("level", "level-"+ str(self.id))
            self.add(self.level)
            self.level.link(self.panorama)
    
    def setPan(self, pan):
        self.panorama.set_property("panorama", pan);
    
    def getPan(self):
        return self.panorama.get_property("panorama")
    
    def setVolume(self, vol):
        self.volume.set_property("volume", vol)
        
    def getVolume(self):
        return self.volume.get_property("volume")
    
    def mute(self):
        self.volume.set_property("mute", True)
        
    def unmute(self):
        self.volume.set_property("mute", False)
        
    def isMuted(self):
        return self.volume.get_property("mute")
    
    def _initGhostPads(self):
        volumepad = self.volume.get_pad("src")
        self.ghostpad = gst.GhostPad("src", volumepad)
        
        if (self._enableLevel):
            sink = self.level.get_pad("sink")
        else:
            sink = self.panorama.get_pad("sink")
        self.sinkpad = gst.GhostPad("sink", sink)
        
        self.add_pad(self.ghostpad)
        self.add_pad(self.sinkpad)
        
gobject.type_register( MixerBin )
        
class Mixer(gobject.GObject):
    
    def __init__(self, mixerbin, controller, id, active=True):
        self.mixerbin = mixerbin
        self.controller = controller
        self.id = id
        self.active = active
    
    def getPanRange(self):
        return -1, 1
    
    def getVolumeRange(self):
        return 0, 1
    
    def getID(self):
        return self.id
    
    def mute(self):
        self.mixerbin.mute()
        
    def unmute(self):
        self.mixerbin.unmute()
        
    def isMuted(self):
        return self.mixerbin.isMuted()
    
    def setPan(self, pan):
        self.mixerbin.setPan(pan)
        
    def getPan(self):
        return self.mixerbin.getPan()
    
    def setVolume(self, vol):
        self.mixerbin.setVolume(vol)
        
    def getVolume(self):
        return self.mixerbin.getVolume()
    
    def remove(self):
        if (self.controller != None):
            self.controller.enqueueRemoval(self.id)
          
    def setInactive(self):
        self.active = False
        
    def setActive(self):
        self.active = True
        
    def isActive(self):
        return self.active

gobject.type_register( Mixer )

class InputManager:
    
    def __init__(self, inputElements, mixerBin, mixer):
        self.inputElements = inputElements
        self.mixerBin = mixerBin
        self.mixerController = mixer
        
    def getMixer(self):
        return self.mixerController
    
    def getMixerBin(self):
        return self.mixerBin
    
    def getInputElements(self):
        return self.inputElements
    
    def getID(self):
        return self.mixerController.getID()
    
        
    def isActive(self):
        return self.mixerController.isActive()
    
    def setInactive(self):
        self.mixerController.setInactive()
    
class FileInputManager(InputManager):
    
    def __init__(self, inputElement, mixerBin, mixer):
        InputManager.__init__(self, inputElement, mixerBin, mixer)
        
    def getFilePath(self):
        return self.inputElement[0].get_property("location")
    
class NetworkInputManager(InputManager):
    
    def __init__(self, inputElement, mixerBin, mixer, session, ssrc, pt):
        InputManager.__init__(self, inputElement, mixerBin, mixer)
        self.session = session
        self.ssrc = ssrc
        self.pt = pt
        
    def getSession(self):
        return self.session
    
    def getSsrc(self):
        return self.ssrc
    
    def getPt(self):
        return self.pt

class TivoManager:
    
    def __init__(self, pipeline):
        self._tees = {}
        self._tivos = {}
        self._pipeline= pipeline
    
    def addTee(self, id, tee):
        self._tees[id] = tee
        
    def removeTee(self, id):
        for tivo in self._tivos.values():
            tivo.removeTee(id)
        del self._tees[id]
    
    def startTivo(self, ids, destFile):
        if (ids == None or len(ids) == 0):
            raise IllegalArgument, "id list must be non-empty"
        if (destFile == None):
            raise IllegalArgument, "destination file can't be None"
        
        filesink = gst.element_factory_make('filesink', 'tivo-sink-' + str(destFile))
        filesink.set_property('location', destFile)
        encoder = gst.element_factory_make('mulawenc', 'tivo-encoder-' + str(destFile))
        encqueue = gst.element_factory_make('queue', 'tivo-queue-' + str(destFile))
        liveadder = gst.element_factory_make('liveadder', 'tivo-adder-' + str(destFile))
        
        self._pipeline.add(filesink)
        self._pipeline.add(encoder)
        self._pipeline.add(encqueue)
        self._pipeline.add(liveadder)
        encoder.link(filesink)
        encqueue.link(encoder)
        liveadder.link(encqueue)
        
        filesink.sync_state_with_parent()
        encoder.sync_state_with_parent()
        encqueue.sync_state_with_parent()
        liveadder.sync_state_with_parent()
        
        queuelist = {}
        padlist = {}

        for x in ids:
            tee = self._tees[x]
            queue = gst.element_factory_make("queue", 'tivo-queue-' + 
                                             str(destFile) + '-' + str(x))
            self._pipeline.add(queue)
            queue.link(liveadder)
            queue.sync_state_with_parent()
            
            queuelist[x] = queue
            pad = tee.get_request_pad("src%d")
            padlist[x] = (tee,pad)
            pad.link(queue.get_pad("sink"))
                   
                
        tmu = TivoUnit(self._pipeline, filesink, encoder, encqueue, liveadder, queuelist, padlist)
        self._tivos[destFile] = tmu
        return tmu
    
    def _sendNewSegment(self, pad):
        event = gst.event_new_new_segment(False, 1, gst.FORMAT_TIME, 0, -1,0)
        pad.push_event(event)
    
    def stop(self):
        for x in self._tivos:
            x.stop()
            del self._tivos[x]
            

TIVO_STATE_RUNNING = "running"
TIVO_STATE_STOPPING = "stopping"
TIVO_STATE_STOPPED = "stopped"

class TivoUnit:
    
    def __init__(self, pipe, sink, enc, encqueue, adder, queueMap, padMap):
        self._pipeline = pipe
        self._sink = sink
        self._enc = enc
        self._encQueue = encqueue
        self._adder = adder
        self._queues = queueMap
        self._pads = padMap
        self._state = TIVO_STATE_RUNNING
        
        
    def getIDList(self):
        return self._pads.keys()
        
    def stop(self):
        for x in self._pads:
           self.unlinkPad(x)           
        
        handler_id = 0
        self._state = TIVO_STATE_STOPPING
        
        if (self._encQueue.get_property("current-level-buffers") == 0):
            self._stopTivo()
        else:
            bus = self._pipeline.get_bus()
            handler_id = bus.add_signal_watch()
            
    def removeTee(self, id):
        print "received remove tee " + str(id)
        self.unlinkPad(id)
        q = self._queues[id]
        peer = q.get_pad("src").get_peer()
        self._adder.release_request_pad(peer)
        self._pipeline.remove(q)
    
    def _messageHandler(self, message):
        t = message.type
        
        if t == gst.MESSAGE_EOS:
            if (message.src == self._sink):
                bus = self._pipeline.get_bus()
                bus.remove_signal_watch()
                
                self._stopTivo()
                return False
            else:
                print "eos not from our sink"
            
        return True
    
    def _stopTivo(self):
        print "stopping tivo"
        for x in self._queues.keys():
            q = self._queues[x]
            q.set_state(gst.STATE_NULL)
            self._pipeline.remove(q)
            self._queues[id] = None
            self._pads[id] = None
        
        self._adder.set_state(gst.STATE_NULL)
        self._enc.set_state(gst.STATE_NULL)
        self._encQueue.set_state(gst.STATE_NULL)
        self._sink.set_state(gst.STATE_NULL)
        self._pipeline.remove(self._adder)
        self._pipeline.remove(self._enc)
        self._pipeline.remove(self._encQueue)
        self._pipeline.remove(self._sink)
        self._state = TIVO_STATE_STOPPED
    
    def unlinkPad(self, id):
        tee,pad = self._pads[id]
        tee.release_request_pad(pad)
        q = self._queues[id]
        pad = q.get_pad('sink')
        pad.send_event(gst.event_new_eos())
        
